In [320]:
# %load jupyter_default.py
import pandas as pd
import numpy as np
import os
import re
import datetime
import time
import glob
import json
from tqdm import tqdm_notebook
from colorama import Fore, Style

%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.colors
import seaborn as sns

%config InlineBackend.figure_format='retina'
sns.set() # Revert to matplotlib defaults
plt.rcParams['figure.figsize'] = (12, 8)
plt.rcParams['axes.labelpad'] = 20
plt.rcParams['legend.fancybox'] = True
plt.style.use('ggplot')

SMALL_SIZE, MEDIUM_SIZE, BIGGER_SIZE = 14, 16, 20
plt.rc('font', size=SMALL_SIZE)
plt.rc('axes', titlesize=SMALL_SIZE)
plt.rc('axes', labelsize=MEDIUM_SIZE)
plt.rc('xtick', labelsize=SMALL_SIZE)
plt.rc('ytick', labelsize=SMALL_SIZE)
plt.rc('legend', fontsize=MEDIUM_SIZE)
plt.rc('axes', titlesize=BIGGER_SIZE)

def savefig(name):
    plt.savefig(f'../../figures/{name}.png', bbox_inches='tight', dpi=300)

%reload_ext autoreload
%autoreload 2
    
%reload_ext version_information
%version_information pandas, numpy
Out[320]:
SoftwareVersion
Python3.6.8 64bit [GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]
IPython6.4.0
OSDarwin 16.7.0 x86_64 i386 64bit
pandas0.23.4
numpy1.14.2
Fri May 03 15:11:36 2019 PDT
In [386]:
from google.cloud import bigquery
from slugify import slugify
In [3]:
from dotenv import load_dotenv
load_dotenv('../../.env')
Out[3]:
True
In [4]:
client = bigquery.Client()

Sales Forecasting

Alex's development notebook for sales forecasting.

Read data

Read from bigquery

In [14]:
table_id = 'ga_sessions_20160801'
query_job = client.query('''
    select date, sum(totals.totalTransactionRevenue)
    from `bigquery-public-data.google_analytics_sample.{}`
    group by date
    limit 10;
'''.format(table_id))
results = query_job.result()
In [18]:
dict(results)
Out[18]:
{'20160801': 6288060000}
In [49]:
%%time
"""
Using bigquery
"""

def pull_daily_data(verbose=False):
    dataset = client.get_dataset('bigquery-public-data.google_analytics_sample')

    data = []
    for table in tqdm_notebook(list(client.list_tables(dataset))):
        if verbose:
            print('Querying {}'.format(table.table_id))
        query_job = client.query('''
            select
              date,
              sum(totals.visits),
              sum(totals.pageviews),
              sum(totals.transactions),
              sum(totals.transactionRevenue)
            from `bigquery-public-data.google_analytics_sample.{}`
            group by date;
        '''.format(table.table_id))
        results = query_job.result().to_dataframe()
        results.columns = ['date', 'visits', 'pageviews', 'transactions', 'transactionRevenue']
        data.append(results)

    df = pd.concat(data, ignore_index=True, sort=False)
    return df

bq_results = pull_daily_data()
CPU times: user 7.94 s, sys: 856 ms, total: 8.8 s
Wall time: 11min 29s
In [53]:
bq_results.head()
Out[53]:
date visits pageviews transactions transactionRevenue
0 20160801 1711 9843 34 6116060000
1 20160802 2140 11784 18 1361190000
2 20160803 2890 13724 None None
3 20160804 3161 13326 17 1182890000
4 20160805 2702 13585 42 5594260000

Read jsonl from google drive

In [7]:
%%time
"""
Using gdrive jsonl
"""
ERRORS = []

def pull_daily_data(verbose=False, raise_errors=False):
    dataset = sorted(glob.glob('/Volumes/GoogleDrive/My Drive/bigquery_ga_sample/*.jsonl'))

    data = []
    for table in tqdm_notebook(dataset):
        if verbose:
            print('Scanning {}'.format(table))
        with open(table, 'r') as f:
            table_data = []
            for line in f:
                d = json.loads(line)
                date = d['date']
                d = d['totals']
                try:
                    table_data.append([
                        date,
                        d['visits'],
                        d['pageviews'],
                        d['transactions'],
                        d['transactionRevenue'],
                    ])
                except Exception as e:
                    if verbose:
                        print('Error raised when reading row:\n{}'.format(e))
                    ERRORS.append([table, e])
                    if raise_errors:
                        raise(e)

            cols = ['date', 'visits', 'pageviews', 'transactions', 'transactionRevenue']
            results = (
                pd.DataFrame(table_data, columns=cols)
                    .groupby('date')[['visits', 'pageviews', 'transactions', 'transactionRevenue']]
                    .sum().reset_index()
            )
            data.append(results)

    df = pd.concat(data, ignore_index=True, sort=False)
    return df

jsonl_gdrive_results = pull_daily_data()
CPU times: user 7min 55s, sys: 1min 56s, total: 9min 52s
Wall time: 23min 14s
In [8]:
jsonl_gdrive_results
Out[8]:
date visits pageviews transactions transactionRevenue
0 20160801 1711 9843.0 34.0 6.116060e+09
1 20160802 2140 11784.0 18.0 1.361190e+09
2 20160803 2890 13724.0 NaN NaN
3 20160804 3161 13326.0 17.0 1.182890e+09
4 20160805 2702 13585.0 42.0 5.594260e+09
5 20160806 1663 6926.0 10.0 1.891040e+09
6 20160807 1622 7239.0 16.0 2.410730e+09
7 20160808 2815 13179.0 36.0 5.273810e+09
8 20160809 2851 13411.0 44.0 5.596400e+09
9 20160810 2757 13003.0 46.0 4.284210e+09
10 20160811 2667 12860.0 51.0 7.427120e+09
11 20160812 2619 15583.0 73.0 8.091190e+09
12 20160813 1596 6387.0 15.0 2.198710e+09
13 20160814 1801 8074.0 28.0 3.360510e+09
14 20160815 3043 14754.0 68.0 6.220330e+09
15 20160816 2873 14311.0 63.0 5.822290e+09
16 20160817 2799 14028.0 49.0 7.124890e+09
17 20160818 2725 12710.0 48.0 6.130610e+09
18 20160819 2379 11557.0 57.0 3.467300e+09
19 20160820 1664 7776.0 22.0 3.243030e+09
20 20160821 1730 7610.0 26.0 2.365310e+09
21 20160822 2584 12934.0 65.0 1.079184e+10
22 20160823 2754 13005.0 63.0 9.625700e+09
23 20160824 2627 12850.0 56.0 5.632410e+09
24 20160825 2539 14374.0 78.0 1.718621e+10
25 20160826 2359 12163.0 58.0 4.918860e+09
26 20160827 1654 7924.0 18.0 9.150800e+08
27 20160828 1682 8593.0 24.0 3.094520e+09
28 20160829 2454 12270.0 43.0 6.593430e+09
29 20160830 2675 11794.0 36.0 2.883710e+09
... ... ... ... ... ...
336 20170703 2046 6492.0 15.0 1.225810e+09
337 20170704 1938 5740.0 7.0 3.799800e+08
338 20170705 2885 9927.0 42.0 8.029360e+09
339 20170706 2658 8924.0 31.0 3.883850e+09
340 20170707 2450 9266.0 40.0 4.339020e+09
341 20170708 1859 6087.0 14.0 4.549600e+08
342 20170709 1921 6523.0 19.0 7.511000e+08
343 20170710 2769 10183.0 47.0 4.718070e+09
344 20170711 2635 8978.0 42.0 5.369640e+09
345 20170712 2554 9778.0 49.0 3.149340e+09
346 20170713 2741 11060.0 65.0 6.098580e+09
347 20170714 2382 9506.0 47.0 4.228840e+09
348 20170715 1721 6251.0 16.0 7.407200e+08
349 20170716 1766 6610.0 28.0 1.748720e+09
350 20170717 2671 11183.0 53.0 4.616770e+09
351 20170718 2804 11386.0 51.0 1.700296e+10
352 20170719 2514 10573.0 57.0 6.038300e+09
353 20170720 2668 10120.0 41.0 4.688700e+09
354 20170721 2427 10410.0 42.0 3.603880e+09
355 20170722 1724 6484.0 18.0 2.395000e+09
356 20170723 1966 7039.0 16.0 2.246920e+09
357 20170724 2436 9707.0 40.0 4.415620e+09
358 20170725 2631 10728.0 38.0 5.240730e+09
359 20170726 2725 11200.0 42.0 4.640450e+09
360 20170727 2529 10175.0 52.0 4.348380e+09
361 20170728 2433 9359.0 46.0 4.709440e+09
362 20170729 1597 6293.0 19.0 1.045010e+09
363 20170730 1799 7258.0 22.0 2.254160e+09
364 20170731 2620 11115.0 62.0 1.141544e+10
365 20170801 2556 10939.0 45.0 8.304940e+09

366 rows × 5 columns

Read jsonl from local

In [139]:
%%time
"""
Using local jsonl
"""
ERRORS = []

def pull_daily_data(verbose=False, raise_errors=False):
    dataset = sorted(glob.glob('../../data/raw/*.jsonl'))

    data = []
    for table in tqdm_notebook(dataset):
        if verbose:
            print('Scanning {}'.format(table))
        with open(table, 'r') as f:
            table_data = []
            for line in f:
                d = json.loads(line)
                date = d['date']
                d = d['totals']
                try:
                    table_data.append([
                        date,
                        d['visits'],
                        d['pageviews'],
                        d['transactions'],
                        d['transactionRevenue'],
                    ])
                except Exception as e:
                    if verbose:
                        print('Error raised when reading row:\n{}'.format(e))
                    ERRORS.append([table, e])
                    if raise_errors:
                        raise(e)

            cols = ['date', 'visits', 'pageviews', 'transactions', 'transactionRevenue']
            results = (
                pd.DataFrame(table_data, columns=cols)
                    .groupby('date')[['visits', 'pageviews', 'transactions', 'transactionRevenue']]
                    .sum().reset_index()
            )
            data.append(results)

    df = pd.concat(data, ignore_index=True, sort=False)
    return df

jsonl_results = pull_daily_data()
CPU times: user 5min 13s, sys: 18.5 s, total: 5min 32s
Wall time: 5min 37s
In [161]:
df = jsonl_results.copy()
df.date = pd.to_datetime(df.date)
df.to_csv('../../data/interim/sales_forecast_raw.csv', index=False)
In [166]:
def load_file(f_path):
    if not os.path.exists(f_path):
        print('No data found. Run data load script above.')
        return
    print('Loading {}'.format(f_path))
    df = pd.read_csv(f_path)
    df.date = pd.to_datetime(df.date)
    return df

# Looking forward to walrus operator for stuff like this...
tmp = load_file('../../data/interim/sales_forecast_raw.csv')
if tmp is not None:
    print('Loading from file')
    df = tmp.copy()
    del tmp
Loading ../../data/interim/sales_forecast.csv
Loading from file
In [167]:
df.head()
Out[167]:
date visits pageviews transactions transactionRevenue
0 2016-08-01 1711 9843.0 34.0 6.116060e+09
1 2016-08-02 2140 11784.0 18.0 1.361190e+09
2 2016-08-03 2890 13724.0 NaN NaN
3 2016-08-04 3161 13326.0 17.0 1.182890e+09
4 2016-08-05 2702 13585.0 42.0 5.594260e+09
In [168]:
df['week'] = df.date.apply(lambda x: x.strftime('%W'))
df['year'] = df.date.apply(lambda x: x.strftime('%Y'))
df['week_start'] = df[['week', 'year']].apply(
    lambda x: datetime.datetime.strptime('{}-{}-1'.format(x.year, x.week), '%Y-%W-%w'),
    axis=1
)
In [169]:
df.dtypes
Out[169]:
date                  datetime64[ns]
visits                         int64
pageviews                    float64
transactions                 float64
transactionRevenue           float64
week                          object
year                          object
week_start            datetime64[ns]
dtype: object

How does the data look? Is there seasonality that I can predict?

In [170]:
df.visits.plot()
Out[170]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a0a56abe0>
In [171]:
df.groupby('week_start').visits.sum().plot()
Out[171]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a1caa32e8>
In [172]:
df.transactions.plot()
Out[172]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a0a04b710>
In [173]:
df.groupby('week_start').transactions.sum().plot()
Out[173]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a0a04b9b0>

We should throw out the first and last week (for these charts)

In [174]:
df_ = df[(df.week_start > df.week_start.min()) & (df.week_start < df.week_start.max())].copy()
In [175]:
df_.groupby('week_start').visits.sum().plot()
Out[175]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a1829de80>
In [176]:
df_.groupby('week_start').transactions.sum().plot()
Out[176]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a185081d0>

That's better!

Forcasting with Facebook Prophet

In [177]:
from fbprophet import Prophet

Transactions

In [178]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet()
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [179]:
fig2 = m.plot_components(forecast)

Trying to fit the data better

In [180]:
Prophet?

Weekly seasonality

In [181]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Tuning changepoint_prior_scale

In [182]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, changepoint_prior_scale=1)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [183]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, changepoint_prior_scale=.1)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [184]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, changepoint_prior_scale=.5)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)

plt.ylim(0, 100)
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
Out[184]:
(0, 100)

Trying out daily_seasonality

In [205]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(daily_seasonality=True, weekly_seasonality=True)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

This looks better! The trick is to add yearly_seasonality

In [185]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, yearly_seasonality=True)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [186]:
fig2 = m.plot_components(forecast)
In [191]:
Prophet?

n_changepoints

In [194]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, yearly_seasonality=True, n_changepoints=2)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [202]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, yearly_seasonality=True, n_changepoints=0)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

seasonality_prior_scale

In [197]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, yearly_seasonality=True, seasonality_prior_scale=0.01)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [204]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, yearly_seasonality=True, daily_seasonality=False, seasonality_prior_scale=0.1)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Sales

In [207]:
df_prophet = df[['date', 'transactionRevenue']]\
    .rename(columns={'date': 'ds', 'transactionRevenue': 'y'})
df_prophet['y'] = df_prophet['y'] / 1e6
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True,
            yearly_seasonality=True,
            daily_seasonality=False,
            seasonality_prior_scale=0.1)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Here we give more weight to the yearly seasonality prior, but it predicts too aggrestive growth for me

In [208]:
df_prophet = df[['date', 'transactionRevenue']]\
    .rename(columns={'date': 'ds', 'transactionRevenue': 'y'})
df_prophet['y'] = df_prophet['y'] / 1e6
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True,
            yearly_seasonality=True,
            daily_seasonality=False,
            seasonality_prior_scale=1)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
fig1 = m.plot(forecast)
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Predictions by quarter

In [251]:
df_prophet = df[['date', 'transactions']]\
    .rename(columns={'date': 'ds', 'transactions': 'y'})
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True, yearly_seasonality=True, daily_seasonality=False, seasonality_prior_scale=0.1)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [252]:
forecast['date'] = pd.to_datetime(forecast['ds'])
In [253]:
def add_quarters(df):
    df['quarter'] = float('nan')
    df['quarter_num'] = float('nan')
    
    df.loc[(df.date >= datetime.datetime(2016, 7, 1))&(df.date < datetime.datetime(2016, 10, 1)), 'quarter'] = '2016 Q3'
    df.loc[(df.date >= datetime.datetime(2016, 10, 1))&(df.date < datetime.datetime(2017, 1, 1)), 'quarter'] = '2016 Q4'
    df.loc[(df.date >= datetime.datetime(2017, 1, 1))&(df.date < datetime.datetime(2017, 4, 1)), 'quarter'] = '2017 Q1'
    df.loc[(df.date >= datetime.datetime(2017, 4, 1))&(df.date < datetime.datetime(2017, 7, 1)), 'quarter'] = '2017 Q2'
    df.loc[(df.date >= datetime.datetime(2017, 7, 1))&(df.date < datetime.datetime(2017, 10, 1)), 'quarter'] = '2017 Q3'
    df.loc[(df.date >= datetime.datetime(2017, 10, 1))&(df.date < datetime.datetime(2018, 1, 1)), 'quarter'] = '2017 Q4'
    df.loc[(df.date >= datetime.datetime(2018, 1, 1))&(df.date < datetime.datetime(2018, 4, 1)), 'quarter'] = '2018 Q1'
    df.loc[(df.date >= datetime.datetime(2018, 4, 1))&(df.date < datetime.datetime(2018, 7, 1)), 'quarter'] = '2018 Q2'
    df.loc[(df.date >= datetime.datetime(2018, 7, 1))&(df.date < datetime.datetime(2018, 10, 1)), 'quarter'] = '2018 Q3'
    df.loc[(df.date >= datetime.datetime(2018, 10, 1))&(df.date < datetime.datetime(2019, 1, 1)), 'quarter'] = '2018 Q4'

    df.loc[(df.date >= datetime.datetime(2016, 7, 1))&(df.date < datetime.datetime(2016, 10, 1)), 'quarter_num'] = 1
    df.loc[(df.date >= datetime.datetime(2016, 10, 1))&(df.date < datetime.datetime(2017, 1, 1)), 'quarter_num'] = 2
    df.loc[(df.date >= datetime.datetime(2017, 1, 1))&(df.date < datetime.datetime(2017, 4, 1)), 'quarter_num'] = 3
    df.loc[(df.date >= datetime.datetime(2017, 4, 1))&(df.date < datetime.datetime(2017, 7, 1)), 'quarter_num'] = 4
    df.loc[(df.date >= datetime.datetime(2017, 7, 1))&(df.date < datetime.datetime(2017, 10, 1)), 'quarter_num'] = 5
    df.loc[(df.date >= datetime.datetime(2017, 10, 1))&(df.date < datetime.datetime(2018, 1, 1)), 'quarter_num'] = 6
    df.loc[(df.date >= datetime.datetime(2018, 1, 1))&(df.date < datetime.datetime(2018, 4, 1)), 'quarter_num'] = 7
    df.loc[(df.date >= datetime.datetime(2018, 4, 1))&(df.date < datetime.datetime(2018, 7, 1)), 'quarter_num'] = 8
    df.loc[(df.date >= datetime.datetime(2018, 7, 1))&(df.date < datetime.datetime(2018, 10, 1)), 'quarter_num'] = 9
    df.loc[(df.date >= datetime.datetime(2018, 10, 1))&(df.date < datetime.datetime(2019, 1, 1)), 'quarter_num'] = 10
    return df
In [254]:
forecast = add_quarters(forecast)
In [255]:
forecast.quarter.value_counts()
Out[255]:
2017 Q3    92
2017 Q4    92
2016 Q4    92
2018 Q2    91
2017 Q2    91
2018 Q1    90
2017 Q1    90
2016 Q3    61
2018 Q3    32
Name: quarter, dtype: int64

We only have partial data for 2016 & 2018 Q3, so we'll want to filter these out

In [256]:
m = (forecast.quarter != '2016 Q3') & (forecast.quarter != '2018 Q3')
s_transactions = (
    forecast[m].groupby(['quarter_num', 'quarter'])
        .yhat.sum().reset_index()
        .set_index('quarter_num').sort_index(ascending=True)
        .set_index('quarter')['yhat']
)
s_transactions.apply(lambda x: '{:,}'.format(round(x)))
Out[256]:
quarter
2016 Q4    3,354
2017 Q1    2,404
2017 Q2    3,054
2017 Q3    4,300
2017 Q4    4,930
2018 Q1    3,934
2018 Q2    4,608
Name: yhat, dtype: object
In [257]:
df_prophet = df[['date', 'transactionRevenue']]\
    .rename(columns={'date': 'ds', 'transactionRevenue': 'y'})
df_prophet['y'] = df_prophet['y'] / 1e6
df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

m = Prophet(weekly_seasonality=True,
            yearly_seasonality=True,
            daily_seasonality=False,
            seasonality_prior_scale=0.1)
m.fit(df_prophet)

future = m.make_future_dataframe(periods=365, freq='D')
forecast = m.predict(future)

forecast['date'] = pd.to_datetime(forecast['ds'])
forecast = add_quarters(forecast)

m = (forecast.quarter != '2016 Q3') & (forecast.quarter != '2018 Q3')
s_transactionRevenue = (
    forecast[m].groupby(['quarter_num', 'quarter'])
        .yhat.sum().reset_index()
        .set_index('quarter_num').sort_index(ascending=True)
        .set_index('quarter')['yhat']
)
s_transactionRevenue.apply(lambda x: '${:,}'.format(round(x)))
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
Out[257]:
quarter
2016 Q4    $389,013
2017 Q1    $337,926
2017 Q2    $395,713
2017 Q3    $493,543
2017 Q4    $509,898
2018 Q1    $454,149
2018 Q2    $515,331
Name: yhat, dtype: object

Display these results in a dataframe

In [261]:
# Get the actual sales (as opposed to predicted above)

df = add_quarters(df)
m = (df.quarter != '2016 Q3') & (df.quarter != '2018 Q3')
s_actual_transactionRevenue = (
    df[m].groupby(['quarter_num', 'quarter'])
        .transactionRevenue.sum().reset_index()
        .set_index('quarter_num').sort_index(ascending=True)
        .set_index('quarter')['transactionRevenue'] / 1e6
)
m.apply(lambda x: '${:,}'.format(round(x)))
Out[261]:
quarter
2016 Q4    $386,901
2017 Q1    $337,599
2017 Q2    $402,070
2017 Q3    $132,804
Name: transactionRevenue, dtype: object
In [268]:
forecast_results = pd.DataFrame({
    'Reporting Period': ['Q4', 'Q1', 'Q2'],
    'Prev Year': [386901, 337599, 402070],
    'Forecasted': [509898, 454149, 515331],
})
forecast_results['YoY (%)'] = ((forecast_results['Forecasted'] - forecast_results['Prev Year'])
                                / forecast_results['Prev Year'] * 100).apply(lambda x: '{:+.0f}%'.format(x))
forecast_results['Prev Year'] = forecast_results['Prev Year'].apply(lambda x: '{:,}'.format(x))
forecast_results['Forecasted'] = forecast_results['Forecasted'].apply(lambda x: '{:,}'.format(x))
forecast_results.set_index('Reporting Period', inplace=True)
forecast_results.to_csv('../../data/interim/sales_forecast.csv')
In [269]:
forecast_results
Out[269]:
Prev Year Forecasted YoY (%)
Reporting Period
Q4 386,901 509,898 +32%
Q1 337,599 454,149 +35%
Q2 402,070 515,331 +28%

Forecasting by product

Looking a couple samples...

In [286]:
def search():
    data = []
    i = 0
    for f_name in glob.glob('../../data/raw/*.jsonl'):
        print('scanning {}'.format(f_name))
        with open(f_name, 'r') as f:
            for line in tqdm_notebook(f.readlines()):
                i += 1
                d = json.loads(line.strip())
                if d['totals']['transactions']:
                    data.append(d)
                    return data

print(json.dumps(search()))

# with open('/tmp/temp.json', 'w') as f:
#     json.dump(data, f)
scanning ../../data/raw/ga_sessions_20160801.jsonl
[{"visitorId": null, "visitNumber": 1, "visitId": 1470106850, "visitStartTime": 1470106850, "date": "20160801", "totals": {"visits": 1, "hits": 16, "pageviews": 13, "timeOnSite": 571, "bounces": null, "transactions": 1, "transactionRevenue": 16990000, "newVisits": 1, "screenviews": null, "uniqueScreenviews": null, "timeOnScreen": null, "totalTransactionRevenue": 23990000, "sessionQualityDim": null}, "trafficSource": {"referralPath": "/yt/about/", "campaign": "(not set)", "source": "youtube.com", "medium": "referral", "keyword": null, "adContent": null, "adwordsClickInfo": {"campaignId": null, "adGroupId": null, "creativeId": null, "criteriaId": null, "page": null, "slot": null, "criteriaParameters": "not available in demo dataset", "gclId": null, "customerId": null, "adNetworkType": null, "targetingCriteria": null, "isVideoAd": null}, "isTrueDirect": null, "campaignCode": null}, "device": {"browser": "Safari", "browserVersion": "not available in demo dataset", "browserSize": "not available in demo dataset", "operatingSystem": "Macintosh", "operatingSystemVersion": "not available in demo dataset", "isMobile": false, "mobileDeviceBranding": "not available in demo dataset", "mobileDeviceModel": "not available in demo dataset", "mobileInputSelector": "not available in demo dataset", "mobileDeviceInfo": "not available in demo dataset", "mobileDeviceMarketingName": "not available in demo dataset", "flashVersion": "not available in demo dataset", "javaEnabled": null, "language": "not available in demo dataset", "screenColors": "not available in demo dataset", "screenResolution": "not available in demo dataset", "deviceCategory": "desktop"}, "geoNetwork": {"continent": "Americas", "subContinent": "Northern America", "country": "United States", "region": "Pennsylvania", "metro": "Pittsburgh PA", "city": "Pittsburgh", "cityId": "not available in demo dataset", "networkDomain": "unknown.unknown", "latitude": "not available in demo dataset", "longitude": "not available in demo dataset", "networkLocation": "not available in demo dataset"}, "customDimensions": [{"index": 4, "value": "North America"}], "hits": [{"hitNumber": 1, "time": 0, "hour": 20, "minute": 0, "isSecure": null, "isInteraction": true, "isEntrance": true, "isExit": null, "referer": "https://www.youtube.com/yt/about/", "page": {"pagePath": "/home", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "Home", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/home", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/home", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(entrance)", "previousContentGroup2": "(entrance)", "previousContentGroup3": "(entrance)", "previousContentGroup4": "(entrance)", "previousContentGroup5": "(entrance)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 2, "time": 20332, "hour": 20, "minute": 1, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/google+redesign/shop+by+brand/youtube", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "YouTube", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/google+redesign/", "pagePathLevel2": "/shop+by+brand/", "pagePathLevel3": "/youtube", "pagePathLevel4": ""}, "transaction": {"transactionId": null, "transactionRevenue": null, "transactionTax": null, "transactionShipping": null, "affiliation": null, "currencyCode": "USD", "localTransactionRevenue": null, "localTransactionTax": null, "localTransactionShipping": null, "transactionCoupon": null}, "item": {"transactionId": null, "productName": null, "productCategory": null, "productSku": null, "itemQuantity": null, "itemRevenue": null, "currencyCode": "USD", "localItemRevenue": null}, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/google+redesign/shop+by+brand/youtube", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [{"productSKU": "GGOEYDHJ056099", "v2ProductName": "22 oz YouTube Bottle Infuser", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 4990000, "localProductPrice": 4990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 1}, {"productSKU": "GGOEYDHJ019399", "v2ProductName": "24 oz YouTube Sergeant Stripe Bottle", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 7990000, "localProductPrice": 7990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 2}, {"productSKU": "GGOEYHPA003510", "v2ProductName": "YouTube Trucker Hat", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 30990000, "localProductPrice": 30990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 3}, {"productSKU": "GGOEGAAX0335", "v2ProductName": "YouTube Men's Long Sleeve Pullover Badge Tee Heather", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 36990000, "localProductPrice": 36990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 4}, {"productSKU": "GGOEGAAX0314", "v2ProductName": "YouTube Men's 3/4 Sleeve Henley", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 24990000, "localProductPrice": 24990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 5}, {"productSKU": "GGOEGAAX0346", "v2ProductName": "YouTube Men's Vintage Tee", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 16990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 6}, {"productSKU": "GGOEGAAX0317", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee White", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 16990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 7}, {"productSKU": "GGOEGAAX0318", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee Black", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 16990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 8}, {"productSKU": "GGOEGAAX0284", "v2ProductName": "Women's YouTube Short Sleeve Hero Tee Black", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 16990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 9}, {"productSKU": "GGOEGAAX0300", "v2ProductName": "YouTube Women's Racer Back Tank Black", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 18990000, "localProductPrice": 18990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 10}, {"productSKU": "GGOEGAAX0296", "v2ProductName": "YouTube Women's Short Sleeve Tri-blend Badge Tee Grey", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 18990000, "localProductPrice": 18990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 11}, {"productSKU": "GGOEGAAX0325", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee Charcoal", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 18990000, "localProductPrice": 18990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 12}, {"productSKU": "GGOEGAAX0356", "v2ProductName": "YouTube Men's Vintage Tank", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 20990000, "localProductPrice": 20990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Category", "productListPosition": 13}], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "Brands", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "(not set)", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": 1, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 3, "time": 68641, "hour": 20, "minute": 1, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/google+redesign/shop+by+brand/youtube/quickview", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "YouTube", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/google+redesign/", "pagePathLevel2": "/shop+by+brand/", "pagePathLevel3": "/youtube/", "pagePathLevel4": "/quickview"}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/google+redesign/shop+by+brand/youtube/quickview", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [{"productSKU": "GGOEGAAX0317", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee White", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 0, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": null, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "(not set)", "productListPosition": 7}], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "2", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "YouTube", "contentGroup2": "Brands", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Brands", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": 1, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 4, "time": 68641, "hour": 20, "minute": 1, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/google+redesign/shop+by+brand/youtube", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "YouTube", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/google+redesign/", "pagePathLevel2": "/shop+by+brand/", "pagePathLevel3": "/youtube", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/google+redesign/shop+by+brand/youtube", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": {"eventCategory": "Enhanced Ecommerce", "eventAction": "Quickview Click", "eventLabel": "YouTube Men's Short Sleeve Hero Tee White", "eventValue": null}, "product": [{"productSKU": "GGOEGAAX0317", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee White", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 0, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": null, "isClick": true, "customDimensions": [], "customMetrics": [], "productListName": "(not set)", "productListPosition": 0}], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "1", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "EVENT", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "Brands", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "YouTube", "previousContentGroup2": "Brands", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 5, "time": 84955, "hour": 20, "minute": 2, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/google+redesign/shop+by+brand/youtube", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "YouTube", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/google+redesign/", "pagePathLevel2": "/shop+by+brand/", "pagePathLevel3": "/youtube", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/google+redesign/shop+by+brand/youtube", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": {"eventCategory": "Enhanced Ecommerce", "eventAction": "Product Click", "eventLabel": null, "eventValue": null}, "product": [{"productSKU": "GGOEGAAX0317", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee White", "v2ProductCategory": "Home/Brands/YouTube/", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 0, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": null, "isClick": true, "customDimensions": [], "customMetrics": [], "productListName": "(not set)", "productListPosition": 7}], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "1", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "EVENT", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "Brands", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Brands", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 6, "time": 87687, "hour": 20, "minute": 2, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/google+redesign/apparel/men+s+t+shirts/short+sleeve+hero+tee+black.axd", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "YouTube Men's Short Sleeve Hero Tee White", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/google+redesign/", "pagePathLevel2": "/apparel/", "pagePathLevel3": "/men+s+t+shirts/", "pagePathLevel4": "/short+sleeve+hero+tee+black.axd"}, "transaction": {"transactionId": null, "transactionRevenue": null, "transactionTax": null, "transactionShipping": null, "affiliation": null, "currencyCode": "USD", "localTransactionRevenue": null, "localTransactionTax": null, "localTransactionShipping": null, "transactionCoupon": null}, "item": {"transactionId": null, "productName": null, "productCategory": null, "productSku": null, "itemQuantity": null, "itemRevenue": null, "currencyCode": "USD", "localItemRevenue": null}, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/google+redesign/apparel/men+s+t+shirts/short+sleeve+hero+tee+black.axd", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [{"productSKU": "9182771", "v2ProductName": "Google Women's 1/4 Zip Jacket Charcoal", "v2ProductCategory": "(not set)", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 0, "localProductPrice": 0, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Related Products", "productListPosition": 1}, {"productSKU": "9180753", "v2ProductName": "Android Rise 14 oz Mug", "v2ProductCategory": "(not set)", "productVariant": "(not set)", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 0, "localProductPrice": 0, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": true, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "Related Products", "productListPosition": 2}], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "Apparel", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Brands", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": 1, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 7, "time": 104645, "hour": 20, "minute": 2, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/basket.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "Shopping Cart", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/basket.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/basket.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 8, "time": 118494, "hour": 20, "minute": 2, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/signin.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "The Google Merchandise Store - Log In", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/signin.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/signin.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 9, "time": 172683, "hour": 20, "minute": 3, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/signin.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "The Google Merchandise Store - Log In", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/signin.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/signin.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 10, "time": 184653, "hour": 20, "minute": 3, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/signin.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "The Google Merchandise Store - Log In", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/signin.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/signin.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 11, "time": 190944, "hour": 20, "minute": 4, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/register.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "The Google Merchandise Store - Register", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/register.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/register.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 12, "time": 324705, "hour": 20, "minute": 6, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/register.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "The Google Merchandise Store - Register", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/register.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/register.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 13, "time": 375406, "hour": 20, "minute": 7, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/register.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "The Google Merchandise Store - Register", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/register.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": null, "item": null, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/register.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": {"eventCategory": "Contact Us", "eventAction": "Onsite Click", "eventLabel": "Phone", "eventValue": null}, "product": [], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "0", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "EVENT", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 14, "time": 475379, "hour": 20, "minute": 8, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/payment.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "Payment Method", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/payment.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": {"transactionId": null, "transactionRevenue": null, "transactionTax": null, "transactionShipping": null, "affiliation": null, "currencyCode": "USD", "localTransactionRevenue": null, "localTransactionTax": null, "localTransactionShipping": null, "transactionCoupon": null}, "item": {"transactionId": null, "productName": null, "productCategory": null, "productSku": null, "itemQuantity": null, "itemRevenue": null, "currencyCode": "USD", "localItemRevenue": null}, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/payment.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [{"productSKU": "GGOEYAAQ031714", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee White", "v2ProductCategory": "(not set)", "productVariant": " MD", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 16990000, "productQuantity": 1, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": null, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "(not set)", "productListPosition": 0}], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "5", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 15, "time": 570879, "hour": 20, "minute": 10, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": null, "referer": null, "page": {"pagePath": "/ordercompleted.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "Checkout Confirmation", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/ordercompleted.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": {"transactionId": "ORD2016080112", "transactionRevenue": 23990000, "transactionTax": null, "transactionShipping": 13500000, "affiliation": "Google Merchandise Store", "currencyCode": "USD", "localTransactionRevenue": 23990000, "localTransactionTax": null, "localTransactionShipping": 13500000, "transactionCoupon": "None"}, "item": {"transactionId": "ORD2016080112", "productName": null, "productCategory": null, "productSku": null, "itemQuantity": null, "itemRevenue": null, "currencyCode": "USD", "localItemRevenue": null}, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/ordercompleted.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [{"productSKU": "GGOEYAAQ031714", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee White", "v2ProductCategory": "(not set)", "productVariant": " MD", "productBrand": "(not set)", "productRevenue": 23990000, "localProductRevenue": 23990000, "productPrice": 16990000, "localProductPrice": 16990000, "productQuantity": 1, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": null, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "(not set)", "productListPosition": 0}], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "6", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}, {"hitNumber": 16, "time": 570880, "hour": 20, "minute": 10, "isSecure": null, "isInteraction": true, "isEntrance": null, "isExit": true, "referer": null, "page": {"pagePath": "/ordercompleted.html", "hostname": "shop.googlemerchandisestore.com", "pageTitle": "Checkout Confirmation", "searchKeyword": null, "searchCategory": null, "pagePathLevel1": "/ordercompleted.html", "pagePathLevel2": "", "pagePathLevel3": "", "pagePathLevel4": ""}, "transaction": {"transactionId": "ORD2016080112", "transactionRevenue": null, "transactionTax": null, "transactionShipping": null, "affiliation": "Google Merchandise Store", "currencyCode": "USD", "localTransactionRevenue": null, "localTransactionTax": null, "localTransactionShipping": null, "transactionCoupon": "None"}, "item": {"transactionId": "ORD2016080112", "productName": null, "productCategory": null, "productSku": null, "itemQuantity": null, "itemRevenue": null, "currencyCode": "USD", "localItemRevenue": null}, "contentInfo": null, "appInfo": {"name": null, "version": null, "id": null, "installerId": null, "appInstallerId": null, "appName": null, "appVersion": null, "appId": null, "screenName": "shop.googlemerchandisestore.com/ordercompleted.html", "landingScreenName": "shop.googlemerchandisestore.com/home", "exitScreenName": "shop.googlemerchandisestore.com/ordercompleted.html", "screenDepth": "0"}, "exceptionInfo": {"description": null, "isFatal": true, "exceptions": null, "fatalExceptions": null}, "eventInfo": null, "product": [{"productSKU": "GGOEYAAQ031714", "v2ProductName": "YouTube Men's Short Sleeve Hero Tee White", "v2ProductCategory": "(not set)", "productVariant": " MD", "productBrand": "(not set)", "productRevenue": null, "localProductRevenue": null, "productPrice": 16990000, "localProductPrice": 16990000, "productQuantity": null, "productRefundAmount": null, "localProductRefundAmount": null, "isImpression": null, "isClick": null, "customDimensions": [], "customMetrics": [], "productListName": "(not set)", "productListPosition": 0}], "promotion": [], "promotionActionInfo": null, "refund": null, "eCommerceAction": {"action_type": "6", "step": 1, "option": null}, "experiment": [], "publisher": null, "customVariables": [], "customDimensions": [], "customMetrics": [], "type": "PAGE", "social": {"socialInteractionNetwork": null, "socialInteractionAction": null, "socialInteractions": null, "socialInteractionTarget": null, "socialNetwork": "YouTube", "uniqueSocialInteractions": null, "hasSocialSourceReferral": "Yes", "socialInteractionNetworkAction": " : "}, "latencyTracking": null, "sourcePropertyInfo": null, "contentGroup": {"contentGroup1": "(not set)", "contentGroup2": "(not set)", "contentGroup3": "(not set)", "contentGroup4": "(not set)", "contentGroup5": "(not set)", "previousContentGroup1": "(not set)", "previousContentGroup2": "Apparel", "previousContentGroup3": "(not set)", "previousContentGroup4": "(not set)", "previousContentGroup5": "(not set)", "contentGroupUniqueViews1": null, "contentGroupUniqueViews2": null, "contentGroupUniqueViews3": null, "contentGroupUniqueViews4": null, "contentGroupUniqueViews5": null}, "dataSource": null, "publisher_infos": []}], "fullVisitorId": "4993485206334150199", "userId": null, "channelGrouping": "Social", "socialEngagementType": "Not Socially Engaged"}]

Read from jsonl local

In [304]:
from typing import List, Tuple, Dict
In [305]:
%%time
"""
Using local jsonl
"""
ERRORS = []

def pull_daily_product_sales(
    verbose=False,
    raise_errors=False,
    test=False,
) -> Tuple[pd.DataFrame, dict]:
    dataset = sorted(glob.glob('../../data/raw/*.jsonl'))

    data = []
    for table in tqdm_notebook(dataset):
        if verbose:
            print('Scanning {}'.format(table))
        with open(table, 'r') as f:
            for line in f:
                d = json.loads(line)
                try:
                    if not d['totals']['transactions']:
                        # No purchases, continue to next visitor
                        continue
                    for hit in d['hits']:
                        for product in hit['product']:
                            if product['productRevenue']:
                                data.append({
                                    'date': d['date'],
                                    'visitId': d['visitId'],
                                    'fullVisitorId': d['fullVisitorId'],
                                    'product': product,
                                })
                except Exception as e:
                    if verbose:
                        print('Error raised when reading row:\n{}'.format(e))
                    ERRORS.append([table, e])
                    if raise_errors:
                        raise(e)
                        
        if test and (table == dataset[1]):
            break

    cols_main = ['date', 'visitId', 'fullVisitorId']
    cols_product = [
        'productSKU', 'v2ProductName', 'v2ProductCategory', 'productVariant',
        'productRevenue', 'productQuantity', 'productRefundAmount'
    ]
    df_data = [
        [d.get(col, float('nan')) for col in cols_main]
        + [d['product'].get(col) for col in cols_product]
        for d in data
    ]
    df = pd.DataFrame(df_data, columns=(cols_main+cols_product))
    return df, data

jsonl_product_results, nosql_data = pull_daily_product_sales(raise_errors=True, test=True)
CPU times: user 2.74 s, sys: 105 ms, total: 2.84 s
Wall time: 2.91 s
In [306]:
jsonl_product_results, nosql_data = pull_daily_product_sales()

In [307]:
df = jsonl_product_results.copy()
df.date = pd.to_datetime(df.date)
df.to_csv('../../data/interim/product_sales_forecast_raw.csv', index=False)
In [308]:
def load_file(f_path):
    if not os.path.exists(f_path):
        print('No data found. Run data load script above.')
        return
    print('Loading {}'.format(f_path))
    df = pd.read_csv(f_path)
    df.date = pd.to_datetime(df.date)
    return df

tmp = load_file('../../data/interim/product_sales_forecast_raw.csv')
if tmp is not None:
    print('Loading from file')
    df = tmp.copy()
    del tmp
Loading ../../data/interim/product_sales_forecast_raw.csv
Loading from file
In [309]:
df.head()
Out[309]:
date visitId fullVisitorId productSKU v2ProductName productVariant productRevenue productQuantity productRefundAmount
0 2016-08-01 1470106850 4993485206334150199 GGOEYAAQ031714 YouTube Men's Short Sleeve Hero Tee White MD 23990000 1 NaN
1 2016-08-01 1470084579 3907770685196037697 GGOEYDHJ019399 24 oz YouTube Sergeant Stripe Bottle Single Option Only 12990000 1 NaN
2 2016-08-01 1470069291 7056870819058799369 GGOEGAAQ010417 Google Men's 100% Cotton Short Sleeve Hero Tee... 2XL 22590000 1 NaN
3 2016-08-01 1470096817 6030957980134486247 GGOEYAEB028413 Women's YouTube Short Sleeve Hero Tee Black SM 19990000 1 NaN
4 2016-08-01 1470055898 6203401114057579951 GGOEGDHC018299 22 oz Mini Mountain Bottle BLUE 36350000 15 NaN

Read from bigquery

In [ ]:
%%time
"""
Using bigquery
"""

def pull_daily_product_sales(verbose=False):
    dataset = client.get_dataset('bigquery-public-data.google_analytics_sample')

    data = []
    for table in tqdm_notebook(list(client.list_tables(dataset))):
        if verbose:
            print('Querying {}'.format(table.table_id))
        query_job = client.query('''
        SELECT
            h.item.productName AS other_purchased_products,
            COUNT(h.item.productName) AS quantity
        FROM `bigquery-public-data.google_analytics_sample.{}`,
            UNNEST(hits) as h
        WHERE (
            fullVisitorId IN (
                SELECT fullVisitorId
                FROM `bigquery-public-data.google_analytics_sample.{}`,
                    UNNEST(hits) as h
                WHERE h.item.productName CONTAINS 'Product Item Name A'
                AND totals.transactions>=1
                GROUP BY fullVisitorId
            )
            AND h.item.productName IS NOT NULL
            AND h.item.productName != 'Product Item Name A'
        )
        GROUP BY other_purchased_products
        ORDER BY quantity DESC;
        '''.format(table.table_id, table.table_id))
        results = query_job.result().to_dataframe()
        results.columns = ['date', 'visits', 'pageviews', 'transactions', 'transactionRevenue']
        data.append(results)

    df = pd.concat(data, ignore_index=True, sort=False)
    return df

bq_results = pull_daily_product_sales()
In [ ]:
bq_results.head()

Exploration

What's going on with refunds?

In [338]:
(~(df.productRefundAmount.isnull())).sum()
Out[338]:
0

They are all null. That is good.

How many different products are purchased together?

In [331]:
fig = plt.figure(figsize=(8, 8))
s = df.groupby('visitId').size().value_counts().sort_index(ascending=False)
s.plot.barh(color='b')
Out[331]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a23b1deb8>

How are product quantities distributed?

In [333]:
fig = plt.figure(figsize=(8, 8))
s = df.productQuantity.value_counts().sort_index(ascending=False).tail(20)
s.plot.barh(color='b')
Out[333]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a23f03978>

Top selling products

In [337]:
df.v2ProductName.value_counts(ascending=False).head(10)
Out[337]:
Google Sunglasses                                       1364
Google Laptop and Cell Phone Stickers                    788
Google 22 oz Water Bottle                                784
Google Men's 100% Cotton Short Sleeve Hero Tee White     760
Google Men's 100% Cotton Short Sleeve Hero Tee Black     715
Google Men's 100% Cotton Short Sleeve Hero Tee Navy      561
Google Men's  Zip Hoodie                                 468
Recycled Paper Journal Set                               431
BLM Sweatshirt                                           405
Engraved Ceramic Google Mug                              400
Name: v2ProductName, dtype: int64

Top grossing products

In [339]:
(df.groupby('v2ProductName').productRevenue.sum() / 1e6).sort_values(ascending=False).head(10)
Out[339]:
v2ProductName
Google Men's  Zip Hoodie                                47636.271605
26 oz Double Wall Insulated Bottle                      44454.491527
Google 22 oz Water Bottle                               42995.041996
Leatherette Journal                                     38563.457656
Google Sunglasses                                       35490.321293
Google Metallic Notebook Set                            28846.596814
Google Men's 100% Cotton Short Sleeve Hero Tee Black    28047.612114
Recycled Paper Journal Set                              27918.170430
Google Men's 100% Cotton Short Sleeve Hero Tee White    27856.018891
Google Hard Cover Journal                               25432.673189
Name: productRevenue, dtype: float64

Forecasts by product name

Daily forcasts are choppy...

In [349]:
def product_forcast(df, product_name) -> pd.DataFrame:

    m = df.v2ProductName == product_name
    print('Found {} product transactions'.format(m.sum()))
    if m.sum() == 0:
        print('Returning empty DataFrame')
        return pd.DataFrame

    df_prophet = df[m][['date', 'productRevenue']]\
        .rename(columns={'date': 'ds', 'productRevenue': 'y'})
    df_prophet['y'] = df_prophet['y'] / 1e6
    df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

    m = Prophet(weekly_seasonality=True,
                yearly_seasonality=True,
                daily_seasonality=False,
                seasonality_prior_scale=0.01)
    m.fit(df_prophet)

    future = m.make_future_dataframe(periods=365, freq='D')
    forecast = m.predict(future)
    fig = m.plot(forecast)
    
    return forecast, fig
In [350]:
forecast, fig = product_forcast(df, 'Google Men\'s  Zip Hoodie')

plt.ylim(0, 100)
plt.show()
Found 468 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Weekly forcasts

In [351]:
def product_forcast(df, product_name) -> pd.DataFrame:

    m = df.v2ProductName == product_name
    print('Found {} product transactions'.format(m.sum()))
    if m.sum() == 0:
        print('Returning empty DataFrame')
        return pd.DataFrame

    df_prophet = df[m][['date', 'productRevenue']]\
        .rename(columns={'date': 'ds', 'productRevenue': 'y'})
    df_prophet['y'] = df_prophet['y'] / 1e6
    df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

    m = Prophet(weekly_seasonality=True,
                yearly_seasonality=True,
                daily_seasonality=False,
                seasonality_prior_scale=0.1)
    m.fit(df_prophet)

    future = m.make_future_dataframe(periods=52, freq='7D')
    forecast = m.predict(future)
    fig = m.plot(forecast)
    
    return forecast, fig
In [353]:
forecast, fig = product_forcast(df, 'Google Men\'s  Zip Hoodie')

plt.show()
Found 468 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Trying seasonality_mode='multiplicative'

In [355]:
def product_forcast(df, product_name) -> pd.DataFrame:

    m = df.v2ProductName == product_name
    print('Found {} product transactions'.format(m.sum()))
    if m.sum() == 0:
        print('Returning empty DataFrame')
        return pd.DataFrame

    df_prophet = df[m][['date', 'productRevenue']]\
        .rename(columns={'date': 'ds', 'productRevenue': 'y'})
    df_prophet['y'] = df_prophet['y'] / 1e6
    df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

    m = Prophet(seasonality_mode='multiplicative')
    m.fit(df_prophet)

    future = m.make_future_dataframe(periods=52, freq='7D')
    forecast = m.predict(future)
    fig = m.plot(forecast)
    
    return forecast, fig
In [356]:
forecast, fig = product_forcast(df, 'Google Men\'s  Zip Hoodie')

plt.show()
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
Found 468 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

No arguments...

In [359]:
def product_forcast(df, product_name) -> pd.DataFrame:

    m = df.v2ProductName == product_name
    print('Found {} product transactions'.format(m.sum()))
    if m.sum() == 0:
        print('Returning empty DataFrame')
        return pd.DataFrame

    df_prophet = df[m][['date', 'productRevenue']]\
        .rename(columns={'date': 'ds', 'productRevenue': 'y'})
    df_prophet['y'] = df_prophet['y'] / 1e6
    df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

    m = Prophet()
    m.fit(df_prophet)

    future = m.make_future_dataframe(periods=365, freq='D')
    forecast = m.predict(future)
    fig = m.plot(forecast)
    
    return forecast, fig
In [360]:
forecast, fig = product_forcast(df, 'Google Men\'s  Zip Hoodie')

plt.show()
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
Found 468 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Google sunglasses

In [361]:
forecast, fig = product_forcast(df, 'Google Sunglasses')

plt.show()
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
Found 1364 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [362]:
def product_forcast(df, product_name) -> pd.DataFrame:

    m = df.v2ProductName == product_name
    print('Found {} product transactions'.format(m.sum()))
    if m.sum() == 0:
        print('Returning empty DataFrame')
        return pd.DataFrame

    df_prophet = df[m][['date', 'productRevenue']]\
        .rename(columns={'date': 'ds', 'productRevenue': 'y'})
    df_prophet['y'] = df_prophet['y'] / 1e6
    df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

    m = Prophet(seasonality_mode='multiplicative')
    m.fit(df_prophet)

    future = m.make_future_dataframe(periods=365, freq='D')
    forecast = m.predict(future)
    fig = m.plot(forecast)
    
    return forecast, fig
In [363]:
forecast, fig = product_forcast(df, 'Google Sunglasses')

plt.show()
INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
Found 1364 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [366]:
def product_forcast(df, product_name) -> pd.DataFrame:

    m = df.v2ProductName == product_name
    print('Found {} product transactions'.format(m.sum()))
    if m.sum() == 0:
        print('Returning empty DataFrame')
        return pd.DataFrame

    df_prophet = df[m][['date', 'productRevenue']]\
        .rename(columns={'date': 'ds', 'productRevenue': 'y'})
    df_prophet['y'] = df_prophet['y'] / 1e6
    df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

    m = Prophet(yearly_seasonality=True, weekly_seasonality=True, daily_seasonality=False)
    m.fit(df_prophet)

    future = m.make_future_dataframe(periods=365, freq='D')
    forecast = m.predict(future)
    fig = m.plot(forecast)
    
    return forecast, fig
In [367]:
forecast, fig = product_forcast(df, 'Google Sunglasses')
plt.show()
Found 1364 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [368]:
forecast, fig = product_forcast(df, 'Google Men\'s  Zip Hoodie')
plt.show()
Found 468 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Clearly there was some crazy stuff going on with the hoodies for a few days. It may not be possilbe to predict this kind of event.

I am going to remove these outliers (by computing standard deviation and filtering out e.g. >3s.d.). Then I'll take that missing revenue and add it back into the quarterly predictions, splitting evenly.

In [431]:
def product_forcast(
    df,
    product_name,
    ignore_std=0,
    yearly_seasonality=True,
    weekly_seasonality=True,
    daily_seasonality=False,
) -> pd.DataFrame:

    m = df.v2ProductName == product_name
    if ignore_std:
        len_0 = m.sum()
        m_outliers = df.productRevenue < (df.loc[m, 'productRevenue'].mean() + df.loc[m, 'productRevenue'].std()*ignore_std)
        print('Ignoring {} outlier points'.format(len_0 - m.sum()))
        
    print('Found {} product transactions'.format(m.sum()))
    if m.sum() == 0:
        print('Returning empty DataFrame')
        return pd.DataFrame

    df_prophet = df[m][['date', 'productRevenue']]\
        .rename(columns={'date': 'ds', 'productRevenue': 'y'})
    df_prophet['y'] = df_prophet['y'] / 1e6
    df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

    m = Prophet(yearly_seasonality=yearly_seasonality,
                weekly_seasonality=weekly_seasonality,
                daily_seasonality=daily_seasonality)
    m.fit(df_prophet)

    future = m.make_future_dataframe(periods=365, freq='D')
    forecast = m.predict(future)
    fig = m.plot(forecast)
    
    return forecast, fig
In [432]:
forecast, fig = product_forcast(df, 'Google Sunglasses', ignore_std=1)
plt.show()
Ignoring 0 outlier points
Found 1364 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
In [433]:
forecast, fig = product_forcast(df, 'Google Men\'s  Zip Hoodie', ignore_std=1)
plt.show()
Ignoring 0 outlier points
Found 468 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Weekly seasonality

In [434]:
forecast, fig = product_forcast(df, 'Google Men\'s  Zip Hoodie', ignore_std=1, weekly_seasonality=False)
plt.show()
Ignoring 0 outlier points
Found 468 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):

Making forecasts by quarter

In [435]:
def add_quarters(df):
    df['quarter'] = float('nan')
    df['quarter_num'] = float('nan')
    
    df.loc[(df.date >= datetime.datetime(2016, 7, 1))&(df.date < datetime.datetime(2016, 10, 1)), 'quarter'] = '2016 Q3'
    df.loc[(df.date >= datetime.datetime(2016, 10, 1))&(df.date < datetime.datetime(2017, 1, 1)), 'quarter'] = '2016 Q4'
    df.loc[(df.date >= datetime.datetime(2017, 1, 1))&(df.date < datetime.datetime(2017, 4, 1)), 'quarter'] = '2017 Q1'
    df.loc[(df.date >= datetime.datetime(2017, 4, 1))&(df.date < datetime.datetime(2017, 7, 1)), 'quarter'] = '2017 Q2'
    df.loc[(df.date >= datetime.datetime(2017, 7, 1))&(df.date < datetime.datetime(2017, 10, 1)), 'quarter'] = '2017 Q3'
    df.loc[(df.date >= datetime.datetime(2017, 10, 1))&(df.date < datetime.datetime(2018, 1, 1)), 'quarter'] = '2017 Q4'
    df.loc[(df.date >= datetime.datetime(2018, 1, 1))&(df.date < datetime.datetime(2018, 4, 1)), 'quarter'] = '2018 Q1'
    df.loc[(df.date >= datetime.datetime(2018, 4, 1))&(df.date < datetime.datetime(2018, 7, 1)), 'quarter'] = '2018 Q2'
    df.loc[(df.date >= datetime.datetime(2018, 7, 1))&(df.date < datetime.datetime(2018, 10, 1)), 'quarter'] = '2018 Q3'
    df.loc[(df.date >= datetime.datetime(2018, 10, 1))&(df.date < datetime.datetime(2019, 1, 1)), 'quarter'] = '2018 Q4'

    df.loc[(df.date >= datetime.datetime(2016, 7, 1))&(df.date < datetime.datetime(2016, 10, 1)), 'quarter_num'] = 1
    df.loc[(df.date >= datetime.datetime(2016, 10, 1))&(df.date < datetime.datetime(2017, 1, 1)), 'quarter_num'] = 2
    df.loc[(df.date >= datetime.datetime(2017, 1, 1))&(df.date < datetime.datetime(2017, 4, 1)), 'quarter_num'] = 3
    df.loc[(df.date >= datetime.datetime(2017, 4, 1))&(df.date < datetime.datetime(2017, 7, 1)), 'quarter_num'] = 4
    df.loc[(df.date >= datetime.datetime(2017, 7, 1))&(df.date < datetime.datetime(2017, 10, 1)), 'quarter_num'] = 5
    df.loc[(df.date >= datetime.datetime(2017, 10, 1))&(df.date < datetime.datetime(2018, 1, 1)), 'quarter_num'] = 6
    df.loc[(df.date >= datetime.datetime(2018, 1, 1))&(df.date < datetime.datetime(2018, 4, 1)), 'quarter_num'] = 7
    df.loc[(df.date >= datetime.datetime(2018, 4, 1))&(df.date < datetime.datetime(2018, 7, 1)), 'quarter_num'] = 8
    df.loc[(df.date >= datetime.datetime(2018, 7, 1))&(df.date < datetime.datetime(2018, 10, 1)), 'quarter_num'] = 9
    df.loc[(df.date >= datetime.datetime(2018, 10, 1))&(df.date < datetime.datetime(2019, 1, 1)), 'quarter_num'] = 10
    return df
In [458]:
def product_forcast(
    df,
    product_name,
    ignore_std=0,
    yearly_seasonality=True,
    weekly_seasonality=True,
    daily_seasonality=False,
    add_back_outlier_revenue=False,
) -> pd.DataFrame:

    df_ = df[df.v2ProductName == product_name].groupby('date').productRevenue.sum().reset_index()
    m = pd.Series(True, index=df_.index)
    if ignore_std:
        m = df_.productRevenue < (df_.productRevenue.mean() + df_.productRevenue.std()*ignore_std)
        if add_back_outlier_revenue:
            outlier_sum = df_.loc[~m, 'productRevenue'].sum() / 1e6
        else:
            outlier_sum = 0
        print('Ignoring {} outlier points'.format((~m).sum()))
        
    print('Found {} product transactions'.format(m.sum()))
    if m.sum() == 0:
        print('Returning empty DataFrame')
        return pd.DataFrame()

    df_prophet = df_[m][['date', 'productRevenue']]\
        .rename(columns={'date': 'ds', 'productRevenue': 'y'})
    df_prophet['y'] = df_prophet['y'] / 1e6
    df_prophet['ds'] = df_prophet['ds'].apply(lambda x: x.strftime('%Y-%m-%d'))

    model = Prophet(yearly_seasonality=yearly_seasonality,
                weekly_seasonality=weekly_seasonality,
                daily_seasonality=daily_seasonality)
    model.fit(df_prophet)

    future = model.make_future_dataframe(periods=365, freq='D')
    forecast = model.predict(future)
    model.plot(forecast)
    
    print('Generating quarterly forecasts')
    forecast['date'] = pd.to_datetime(forecast['ds'])
    forecast = add_quarters(forecast)

    m_q = (forecast.quarter != '2016 Q3') & (forecast.quarter != '2018 Q3')
    s_transactions = (
        forecast[m_q].groupby(['quarter_num', 'quarter'])
            .yhat.sum().reset_index()
            .set_index('quarter_num').sort_index(ascending=True)
            .set_index('quarter')['yhat']
    )

    # Get the actual sales (as opposed to predicted above)
    df_ = add_quarters(df_)
    m_q = (df_.quarter != '2016 Q3') & (df_.quarter != '2018 Q3')
    s_actual_productRevenue = (
        df_[m & m_q].groupby(['quarter_num', 'quarter'])
            .productRevenue.sum().reset_index()
            .set_index('quarter_num').sort_index(ascending=True)
            .set_index('quarter')['productRevenue'] / 1e6
    )

    forecast_results = pd.DataFrame({
        'Reporting Period': ['Q4', 'Q1', 'Q2'],
        'Prev Year': [
            s_actual_productRevenue[s_actual_productRevenue.index=='2016 Q4'].values[0] if (s_actual_productRevenue.index=='2016 Q4').sum() else 0,
            s_actual_productRevenue[s_actual_productRevenue.index=='2017 Q1'].values[0] if (s_actual_productRevenue.index=='2017 Q1').sum() else 0,
            s_actual_productRevenue[s_actual_productRevenue.index=='2017 Q2'].values[0] if (s_actual_productRevenue.index=='2017 Q2').sum() else 0,
        ],
        'Forecasted': [
            s_transactions[s_transactions.index=='2017 Q4'].values[0] + outlier_sum/4,
            s_transactions[s_transactions.index=='2018 Q1'].values[0] + outlier_sum/4,
            s_transactions[s_transactions.index=='2018 Q2'].values[0] + outlier_sum/4,
        ],
    })
    forecast_results['YoY (%)'] = ((forecast_results['Forecasted'] - forecast_results['Prev Year'])
                                    / forecast_results['Prev Year'] * 100).apply(lambda x: '{:+.0f}%'.format(x))
    forecast_results['Prev Year'] = forecast_results['Prev Year'].apply(lambda x: '${:,.0f}'.format(x))
    forecast_results['Forecasted'] = forecast_results['Forecasted'].apply(lambda x: '${:,.0f}'.format(x))
    forecast_results.set_index('Reporting Period', inplace=True)
    forecast_results.to_csv('../../data/interim/sales_forecast_{}.csv'.format(slugify(product_name)))
    
    return forecast, forecast_results, fig
In [459]:
forecast, forecast_results, fig = product_forcast(
    df,
    'Google Men\'s  Zip Hoodie',
    ignore_std=1,
    weekly_seasonality=False
)
plt.show()
forecast_results
Ignoring 4 outlier points
Found 218 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
Generating quarterly forecasts
Out[459]:
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $8,666 $10,675 +23%
Q1 $7,168 $9,920 +38%
Q2 $3,548 $6,153 +73%

Run for each of the top 30 Top selling products

In [460]:
for product in df.v2ProductName.value_counts(ascending=False).head(30).index.tolist():
    print('-'*20)
    print(Fore.RED + product + Style.RESET_ALL)
    forecast, forecast_results, fig = product_forcast(
        df,
        product,
        ignore_std=1,
        weekly_seasonality=False
    )
    plt.show()
    display(forecast_results)
--------------------
Google Sunglasses
Ignoring 25 outlier points
Found 257 product transactions
/anaconda3/lib/python3.6/site-packages/pystan/misc.py:399: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  elif np.issubdtype(np.asarray(v).dtype, float):
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $4,399 $4,450 +1%
Q1 $3,857 $4,361 +13%
Q2 $4,779 $4,764 -0%
--------------------
Google Laptop and Cell Phone Stickers
Ignoring 21 outlier points
Found 273 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $2,357 $1,749 -26%
Q1 $1,565 $819 -48%
Q2 $1,825 $1,570 -14%
--------------------
Google 22 oz Water Bottle
Ignoring 25 outlier points
Found 228 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $6,112 $13,291 +117%
Q1 $5,815 $11,240 +93%
Q2 $5,643 $11,673 +107%
--------------------
Google Men's 100% Cotton Short Sleeve Hero Tee White
Ignoring 18 outlier points
Found 258 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $5,229 $-3,355 -164%
Q1 $3,137 $-4,084 -230%
Q2 $3,587 $-4,626 -229%
--------------------
Google Men's 100% Cotton Short Sleeve Hero Tee Black
Ignoring 17 outlier points
Found 247 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $3,689 $7,304 +98%
Q1 $2,797 $6,623 +137%
Q2 $3,464 $6,851 +98%
--------------------
Google Men's 100% Cotton Short Sleeve Hero Tee Navy
Ignoring 16 outlier points
Found 226 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $2,553 $-63 -102%
Q1 $1,687 $-513 -130%
Q2 $1,848 $-294 -116%
--------------------
Google Men's  Zip Hoodie
Ignoring 4 outlier points
Found 218 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $8,666 $10,675 +23%
Q1 $7,168 $9,920 +38%
Q2 $3,548 $6,153 +73%
--------------------
Recycled Paper Journal Set
Ignoring 29 outlier points
Found 160 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $3,932 $10,037 +155%
Q1 $3,028 $7,741 +156%
Q2 $4,682 $10,292 +120%
INFO:fbprophet:n_changepoints greater than number of observations.Using 15.0.
--------------------
BLM Sweatshirt
Ignoring 5 outlier points
Found 20 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $0 $-92,284 -inf%
Q1 $1,433 $-157,462 -11091%
Q2 $5,737 $-214,612 -3841%
--------------------
Engraved Ceramic Google Mug
Ignoring 15 outlier points
Found 162 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $3,446 $-25,828 -850%
Q1 $2,817 $-25,317 -999%
Q2 $256 $-1,358 -630%
--------------------
Google Twill Cap
Ignoring 12 outlier points
Found 218 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $2,611 $2,326 -11%
Q1 $2,094 $1,346 -36%
Q2 $2,114 $1,477 -30%
--------------------
26 oz Double Wall Insulated Bottle
Ignoring 8 outlier points
Found 205 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $6,101 $-5,224 -186%
Q1 $6,824 $-2,180 -132%
Q2 $5,084 $-2,774 -155%
--------------------
Google Men's Vintage Badge Tee Black
Ignoring 7 outlier points
Found 166 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $792 $981 +24%
Q1 $522 $1,134 +117%
Q2 $2,377 $1,835 -23%
--------------------
Google Men's 100% Cotton Short Sleeve Hero Tee Red
Ignoring 17 outlier points
Found 185 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $1,624 $3,162 +95%
Q1 $1,319 $3,530 +168%
Q2 $1,538 $3,414 +122%
--------------------
Red Shine 15 oz Mug
Ignoring 11 outlier points
Found 176 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $2,611 $8,935 +242%
Q1 $1,332 $7,528 +465%
Q2 $1,009 $9,185 +811%
--------------------
Recycled Mouse Pad
Ignoring 7 outlier points
Found 201 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $1,771 $3,510 +98%
Q1 $807 $2,770 +243%
Q2 $1,317 $3,310 +151%
--------------------
YouTube Custom Decals
Ignoring 5 outlier points
Found 212 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $607 $1,229 +102%
Q1 $544 $1,221 +124%
Q2 $747 $1,482 +98%
--------------------
Maze Pen
Ignoring 16 outlier points
Found 181 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $3,016 $6,531 +117%
Q1 $1,964 $5,307 +170%
Q2 $2,259 $5,609 +148%
--------------------
Windup Android
Ignoring 24 outlier points
Found 169 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $790 $3,440 +335%
Q1 $1,131 $3,634 +221%
Q2 $1,081 $3,662 +239%
--------------------
Foam Can and Bottle Cooler
Ignoring 19 outlier points
Found 162 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $1,075 $6,769 +529%
Q1 $767 $7,006 +814%
Q2 $1,288 $7,477 +480%
--------------------
Badge Holder
Ignoring 18 outlier points
Found 168 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $1,288 $-3,946 -406%
Q1 $1,249 $-3,662 -393%
Q2 $1,055 $-4,494 -526%
--------------------
Sport Bag
Ignoring 11 outlier points
Found 176 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $2,720 $7,627 +180%
Q1 $3,653 $8,378 +129%
Q2 $2,950 $8,317 +182%
--------------------
Leatherette Journal
Ignoring 6 outlier points
Found 174 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $4,511 $5,414 +20%
Q1 $5,396 $7,055 +31%
Q2 $5,367 $12,891 +140%
--------------------
Google Women's Short Sleeve Hero Tee White
Ignoring 10 outlier points
Found 142 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $1,439 $5,461 +279%
Q1 $1,088 $4,984 +358%
Q2 $946 $5,648 +497%
--------------------
Google Men's Bike Short Sleeve Tee Charcoal
Ignoring 14 outlier points
Found 126 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $16 $3,316 +20133%
Q1 $1,504 $4,131 +175%
Q2 $2,608 $5,577 +114%
--------------------
Google Metallic Notebook Set
Ignoring 12 outlier points
Found 162 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $3,342 $-4,485 -234%
Q1 $3,823 $-4,816 -226%
Q2 $3,236 $-5,224 -261%
--------------------
Google Kick Ball
Ignoring 10 outlier points
Found 168 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $1,729 $4,201 +143%
Q1 $1,033 $3,364 +226%
Q2 $1,315 $3,577 +172%
--------------------
Google Women's Fleece Hoodie
Ignoring 7 outlier points
Found 142 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $4,631 $9,715 +110%
Q1 $3,071 $8,075 +163%
Q2 $1,751 $7,057 +303%
--------------------
YouTube Leatherette Notebook Combo
Ignoring 17 outlier points
Found 136 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $1,906 $18,428 +867%
Q1 $1,852 $18,188 +882%
Q2 $2,510 $18,340 +631%
--------------------
Google Doodle Decal
Ignoring 10 outlier points
Found 141 product transactions
Generating quarterly forecasts
Prev Year Forecasted YoY (%)
Reporting Period
Q4 $652 $3,138 +381%
Q1 $356 $2,653 +646%
Q2 $562 $2,907 +418%
In [26]:
from IPython.display import HTML
HTML('<style>div.text_cell_render{font-size:130%;padding-top:50px;padding-bottom:50px}</style>')
Out[26]: